package com.hitqz.robot.dispatch.netty.listener;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.shaded.com.google.common.util.concurrent.RateLimiter;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.cache.Cache;
import com.hitqz.robot.api.business.dto.FileChunkDto;
import com.hitqz.robot.api.business.dto.RobotClientSummaryDto;
import com.hitqz.robot.api.business.dto.RobotPositionDto;
import com.hitqz.robot.api.business.dto.RobotSendClientMsgDto;
import com.hitqz.robot.api.business.entity.*;
import com.hitqz.robot.api.dispatch.model.MessageDto;
import com.hitqz.robot.api.dispatch.model.RobotDispatchDto;
import com.hitqz.robot.api.dispatch.protocol.ProtocolEnum;
import com.hitqz.robot.api.dispatch.protocol.dto.*;
import com.hitqz.robot.biz.service.*;
import com.hitqz.robot.biz.websocket.endpoint.SubscriptionEndpoint;
import com.hitqz.robot.biz.websocket.endpoint.SubscriptionLogEndpoint;
import com.hitqz.robot.camera.dto.RealManArmStateDto;
import com.hitqz.robot.common.core.jackson.RobotJavaTimeModule;
import com.hitqz.robot.common.core.util.R;
import com.hitqz.robot.common.core.util.RedisUtils;
import com.hitqz.robot.common.file.core.FileProperties;
import com.hitqz.robot.dispatch.netty.ChannelCache;
import com.hitqz.robot.dispatch.netty.factory.AlgorithmFactory;
import com.hitqz.robot.dispatch.netty.util.JtsUtil;
import com.hitqz.robot.dispatch.netty.util.LockUtil;
import com.hitqz.robot.dispatch.netty.util.RobotReplyUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.Polygon;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;

@Component
@Slf4j
@AllArgsConstructor
public class QueryListener {

	private final FileProperties fileProperties;

	private final RobotService robotService;

	private final RobotMapService robotMapService;

	private final RobotMapPosService robotMapPosService;

	private final RobotMapAreaService robotMapAreaService;

	private final RobotMapAreaPosService robotMapAreaPosService;

	private final TicketsService ticketsService;

	private final TicketsTaskService ticketsTaskService;

	private final RobotWarningRecordService robotWarningRecordService;

	private final RobotGroupService robotGroupService;

	private final AlgorithmFactory algorithmFactory;

	private final BatteryDataInfoService batteryDataInfoService;

	private final BigDecimal THOUSAND = new BigDecimal(2000);


	private static final ObjectMapper objectMapper = new ObjectMapper()
			.registerModule(new RobotJavaTimeModule());
	public static final Map<String, Map<String, String>> topicClientMessages = new ConcurrentHashMap<>(16);

	private static final Map<String,String> CLIENT_MAP_SERVER_URL_MAP = new HashMap<>(8);

	//话题限流
	private static final Map<String, RateLimiter> topicRateLimiterMap = new ConcurrentHashMap<>(8);


	@EventListener
	@Async("serverListenerThreadPool")
	public void handleQuery(RobotMapDto dto) {
		RobotEntity robotEntity = robotService.selectByRobotSn(dto.getClientId());
		if (robotEntity == null) {
			log.info("[netty listener] robot is not registered");
		}
		RobotMapEntity robotMapEntity = robotMapService.findByMapCode(dto.getMapCode());
		if (robotMapEntity == null) {
			robotMapEntity = new RobotMapEntity();
		}
		if (StrUtil.isNotBlank(dto.getDownloadUrl())){
			try{
				String suffix = Objects.equals(dto.getType(),2)? ".pcd" :".png";
				String filePath = StrUtil.isBlank(robotMapEntity.getFilePath()) ?
						Paths.get(fileProperties.getLocal().getBasePath(),"map",dto.getMapCode()+suffix).toFile().getAbsolutePath()
						: Paths.get(fileProperties.getLocal().getBasePath(),robotMapEntity.getFilePath()).toString();
				String relativePath = filePath.replace(Paths.get(fileProperties.getLocal().getBasePath()).toString(),"").replaceAll("\\\\","/");
				//if (StrUtil.isNotBlank(dto.getDownloadUrl())){
				//	try {
				//		log.info("map download url:{}",dto.getDownloadUrl());
				//		URL url = URLUtil.url(dto.getDownloadUrl());
				//		FileUtil.writeFromStream(URLUtil.getStream(url), filePath);
				//		robotMapEntity.setFilePath(relativePath);
				//		log.info("{}，更新地图文件 relativePath:{}", dto.getClientId(), relativePath);
				//	}catch (Exception e) {
				//		log.info("下载地图失败" +e.getMessage());
				//	}
				//
				//}
			}catch (Exception e){
				log.error("sync map download error",e);
			}
		}
		BeanUtil.copyProperties(dto, robotMapEntity, false);
		robotMapService.saveOrUpdate(robotMapEntity);
		//清除旧缓存
		CLIENT_MAP_SERVER_URL_MAP.remove(Optional.of(dto).map(RobotMapDto::getMapCode).orElse("-"));
	}


	@EventListener
	@Async("serverListenerThreadPool")
	public void handleQuery(RobotMapPosDto dto) {
		synchronized (this) {
			RobotEntity robotEntity = robotService.selectByRobotSn(dto.getClientId());
			if (robotEntity == null) {
				log.info("[netty listener] robot is not registered");
			}
			RobotMapPosEntity robotMapPos  = robotMapPosService.getOne(Wrappers.<RobotMapPosEntity>lambdaQuery()
					.eq(RobotMapPosEntity::getMapCode, dto.getMapCode())
					.eq(RobotMapPosEntity::getCode, dto.getCode())
			);
			if (robotMapPos == null) {
				robotMapPos = new RobotMapPosEntity();
			}
			BeanUtil.copyProperties(dto, robotMapPos, false);
			robotMapPosService.saveOrUpdate(robotMapPos);
		}
	}

	@EventListener
	@Async("serverListenerThreadPool")
	public void handleQuery(RobotMapAreaDto dto) throws JsonProcessingException {
		log.info("[netty listener RobotMapAreaDto]收到消息");
		synchronized (this) {
			RobotEntity robotEntity = robotService.selectByRobotSn(dto.getClientId());
			if (robotEntity == null) {
				log.info("[netty listener] robot is not registered");
			} else {
				// 全部删除 然后重新添加
				RobotMapAreaEntity areaEntity = new RobotMapAreaEntity();
				BeanUtil.copyProperties(dto, areaEntity, false);
				robotMapAreaService.save(areaEntity);
				if(CollectionUtil.isNotEmpty(dto.getAreaPos())) {
					for (RobotMapAreaPosDto robotMapAreaPosDto: dto.getAreaPos()) {
						RobotMapAreaPosEntity posEntity = new RobotMapAreaPosEntity();
						BeanUtil.copyProperties(robotMapAreaPosDto, posEntity);
						posEntity.setAreaId(areaEntity.getId());
						robotMapAreaPosService.save(posEntity);
					}
				}
			}
		}

	}


	//高频事件 要避免重复查询，否则可能堆溢出
	@EventListener
	@Async("serverListenerThreadPool")
	public void handleQuery(RobotClientSummaryDto dto) {
		try {
			//通过ws给前端广播消息
			// 保存下最新的信息
			Cache<String, RobotClientSummaryDto> posInfoDtoCache = SpringUtil.getBean("robotCache");
			if (Optional.of(dto).map(RobotClientSummaryDto::getPosition).isPresent()){
				RobotPositionDto position = dto.getPosition();
				String mapCode = position.getMapCode();
				//替换地图 url 为 server 本地的
				if (StrUtil.isNotBlank(mapCode)){
					String mapUrl = CLIENT_MAP_SERVER_URL_MAP.get(mapCode);
					if (mapUrl==null) {
						RobotMapEntity robotMapEntity = robotMapService.findByMapCode(mapCode);
						//存一个空避免重复查
						mapUrl = Optional.ofNullable(robotMapEntity).map(RobotMapEntity::getFilePath).orElse("");
						CLIENT_MAP_SERVER_URL_MAP.put(mapCode, Optional.ofNullable(robotMapEntity).map(RobotMapEntity::getFilePath).orElse(""));
					}
					position.setUrl(mapUrl);
				}
			}
			Cache<String, Polygon> robotPosPolygon = SpringUtil.getBean("robotPosPolygon");
			BigDecimal robotWidth = new BigDecimal(dto.getOtherInfo().getRobotWidth())
					.divide(THOUSAND, 3, RoundingMode.HALF_UP);
			BigDecimal robotLength = new BigDecimal(dto.getOtherInfo().getRobotLength())
					.divide(THOUSAND, 3, RoundingMode.HALF_UP);
			robotPosPolygon.put(dto.getClientId(),
					JtsUtil.getRectangleVertices(dto.getPosition().getLaserPos().getX(),
							dto.getPosition().getLaserPos().getY(),
							robotLength.doubleValue(), robotWidth.doubleValue()));
			posInfoDtoCache.put(dto.getClientId(), dto);
			//
			RobotEntity robotEntity = robotService.selectByRobotSn(dto.getClientId());
			if (robotEntity!=null) {
				dto.setGroupId(robotEntity.getGroupId());
			}
			// 计算位置的区域


			String topic = "summary";//topic应该整理成枚举类
			topicClientMessages.computeIfAbsent(topic,
					k -> new ConcurrentHashMap<>()).put(dto.getClientId(), objectMapper.writeValueAsString(dto));
			broadcastMessageByTopic(topic);


		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
	}

	@EventListener
	@Async("serverListenerThreadPool")
	public void handleQuery(RobotDto dto) {
		RobotEntity robotEntity = robotService.selectByRobotSn(dto.getRobotSn());
		if (robotEntity == null) {
			robotEntity = new RobotEntity();
			robotEntity.setGroupId(1);

		}
		BeanUtil.copyProperties(dto, robotEntity, false);
		robotService.saveOrUpdate(robotEntity);
	}
	@EventListener
	@Async("serverListenerThreadPool")
	public void handleQuery(ReportLogMessageDto reportLogMessageDto) {
		SubscriptionLogEndpoint.broadcast(
				reportLogMessageDto.getRandomId() == null ? null : Integer.valueOf(reportLogMessageDto.getRandomId()),
				reportLogMessageDto.getClientId(), reportLogMessageDto.getMessage());
	}
	@EventListener
	@Async("serverListenerThreadPool")
	public void handleQuery(UploaderLogFileDto uploaderLogFileDto) {
		log.info("收到日志文件地址: {}---{}", uploaderLogFileDto.getClientId(), uploaderLogFileDto.getLogFile());
		Cache<String, String> posInfoDtoCache = SpringUtil.getBean("uploaderLogs");
		posInfoDtoCache.put(uploaderLogFileDto.getClientId(), uploaderLogFileDto.getLogFile());
	}

	@EventListener
	@Async("serverListenerThreadPool")
	public void handleQuery(RobotApplyPathDto dto) {
		synchronized (this) {
			log.info("请求前往目标点:" + dto.getCode() + "---" + dto.getClientId());
			Cache<String, RobotDispatchDto> posInfoDtoCache = SpringUtil.getBean("cacheRobotTask");
			RobotDispatchDto robotDispatchDto = new RobotDispatchDto();
			robotDispatchDto.setClientId(dto.getClientId());
			robotDispatchDto.setPathCodes(dto.getPathList());
			robotDispatchDto.setTargetPosCode(dto.getCode());
			robotDispatchDto.setPathDistances(dto.getPathDistances());
			posInfoDtoCache.put(dto.getClientId(), robotDispatchDto);
			// 回复接受成功 暂时不需要回复回复直接加入调度组缓存里面进行计算
			// 考虑---先暂停队列，后续根据调度的计算判断是否要进行继续
			RobotSendClientMsgDto robotSendClientMsgDto = new RobotSendClientMsgDto();
			robotSendClientMsgDto.setCmd(0);
			robotSendClientMsgDto.setSeq(Integer.parseInt(
					String.valueOf(ProtocolEnum.ROBOT_APPLY_TARGET_POS.getCmd())));
			robotSendClientMsgDto.setClientId(dto.getClientId());
			robotSendClientMsgDto.setData(R.ok());
			SpringUtil.getApplicationContext().publishEvent(robotSendClientMsgDto);
			// 加入调度的数据
			RobotEntity robotEntity = robotService.selectByRobotSn(dto.getClientId());
			if (robotEntity != null) {
				RobotGroupEntity robotGroup =robotGroupService.getById(robotEntity.getGroupId());
				if (robotGroup == null || StrUtil.isEmpty(robotGroup.getDispatchAlgorithm())) {
					log.info("robot has no DispatchAlgorithm or dispatch group {}", dto.getClientId());
					return;
				}
				algorithmFactory.getRoutingStrategy(robotGroup.getDispatchAlgorithm()).changePath(robotGroup, dto);
			}
		}




	}


	@EventListener
	@Async("serverListenerThreadPool")
	public void handlerQuery(FormConfigModalDto dto) {
		//使用redis，而不用cache，避免重启server，而client没有连接，且要去修改旧任务，会没有数据。
		String jsonString = JSONObject.toJSONString(dto);
		RedisUtils.set("dynamicTaskContent:"+dto.getRobotSn(),jsonString);
		log.info("保存动态表单:{},{},{}",dto.getRobotSn(),dto.getFormTitle(),dto.getFormName());
	}



	@EventListener
	@Async("serverListenerThreadPool")
	public void handleQuery(RobotTicketsDto dto) {
		synchronized (this) {
			TicketsEntity tickets = ticketsService.getById(dto.getServerId());
			if (tickets == null) {
				tickets = new TicketsEntity();
			}
			BeanUtil.copyProperties(dto, tickets);
			if (dto.getServerId() != null) {
				tickets.setId(dto.getServerId().longValue());
			}
			ticketsService.saveOrUpdate(tickets);
			dto.setServerId(tickets.getId().intValue());
			if (CollectionUtil.isNotEmpty(dto.getTickets())) {
				for (RobotTicketsTaskDto taskDto: dto.getTickets()) {
					TicketsTaskEntity ticketsTaskEntity = new TicketsTaskEntity();
					if (taskDto.getServerId() != null) {
						ticketsTaskEntity = ticketsTaskService.getById(taskDto.getServerId());
					}
					BeanUtil.copyProperties(taskDto, ticketsTaskEntity, false);
					ticketsTaskEntity.setTicketsId(tickets.getId());

					ticketsTaskService.saveOrUpdate(ticketsTaskEntity);
					taskDto.setServerId(ticketsTaskEntity.getId().intValue());
				}
			}
			// 更新完成后，下发serverId
			MessageDto messageDto= new MessageDto();
			messageDto.setSn(0);
			messageDto.setClientId(dto.getAllotRobotSn());
			messageDto.setCmd(ProtocolEnum.ROBOT_TICKETS.getCmd());
			messageDto.setData(JSONObject.toJSONString(dto).getBytes(StandardCharsets.UTF_8));
			ChannelCache.send2Client(dto.getAllotRobotSn(),
					BeanUtil.copyProperties(messageDto, MessageDto.class));
			// 开始设置，任务时间
			Cache<String, Long> idleTask = SpringUtil.getBean("idleTask");
			idleTask.put(dto.getAllotRobotSn(), System.currentTimeMillis());
		}
	}

	@EventListener
	@Async("serverListenerThreadPool")
	public void handleQuery(MessageDto messageDto) throws IOException {
		ProtocolEnum protocolEnum = ProtocolEnum.CMD_PROTOCOL_MAP.get(messageDto.getCmd());
		//log.info("[netty listener]" + messageDto.getSn());
		String clientId = messageDto.getClientId();
		if (messageDto.getSn() > 0) {
			Lock lock = LockUtil.ROBOT_CMD_TICKETS_LOCKS.get(clientId, (short) messageDto.getSn());
			if (lock != null) {
				RobotReplyUtil.COMMON_REPLY_TABLE.put(clientId, (short) messageDto.getSn(),
						JSONObject.parseObject(new String(messageDto.getData(), StandardCharsets.UTF_8), R.class));
				try {
					lock.notifyAll();
				}catch (Exception e){

				}
			}
		}

	}


	@EventListener
	@Async("serverListenerThreadPool")
	public void handlerQuery(RobotCmdNavDto robotCmdNavDto) {





	}


	//处理分块文件
	@EventListener
	@Async("serverFileThreadPool")
	public void handlerFile(FileChunkDto dto) {


	}



	@EventListener
	@Async("serverFileThreadPool")
	public void handlerBatteryLevel(ReportBatteryLevelDto dto) {
		BatteryDataInfoEntity entity = new BatteryDataInfoEntity();
		entity.setClientId(dto.getClientId());
		entity.setStatisticalTime(dto.getStatisticalTime());
		entity.setBattery(dto.getBattery());
		entity.setCurrent(dto.getCurrent());
		entity.setVoltage(dto.getVoltage());
		entity.setStatisticalTime(dto.getStatisticalTime());
		batteryDataInfoService.save(entity);
	}



	@EventListener
	@Async("serverListenerThreadPool")
	public void handlerWarning(RobotWarningDto robotWarningDto) {
		log.info("Robot Warning" + JSONObject.toJSONString(robotWarningDto));
		RobotWarningRecordEntity robotWarningRecord = new RobotWarningRecordEntity();
		robotWarningRecord.setFlag("0");
		robotWarningRecord.setContent(robotWarningDto.getContent());
		robotWarningRecord.setServiceId(robotWarningDto.getRobotSn());
		robotWarningRecord.setTime(LocalDateTime.now());
		robotWarningRecord.setTitle(robotWarningDto.getWarningObj());
		robotWarningRecord.setType("0");
		robotWarningRecord.setLevel(robotWarningDto.getLevel());
		robotWarningRecordService.save(robotWarningRecord);
	}

	@EventListener
	@Async("serverListenerThreadPool")
	public void handlerRealManArmState(RealManArmStateDto realManArmStateDto) throws Exception {
		String topic = "realManArmState";
		topicClientMessages.computeIfAbsent(topic,
				k -> new ConcurrentHashMap<>()).put("any", objectMapper.writeValueAsString(realManArmStateDto));
		broadcastMessageByTopic(topic);
	}

	public static void broadcastMessageByTopic(String topic) {
		//限流
		RateLimiter rateLimiter = topicRateLimiterMap.computeIfAbsent(topic, k -> RateLimiter.create(1));
		if (rateLimiter.tryAcquire()) {
			Map<String, String> clientMessages = topicClientMessages.getOrDefault(topic, Collections.emptyMap());
			// 创建广播的消息内容，包含所有去重后的消息
			String broadcastMessage = createBroadcastMessage(clientMessages);
			// 广播消息给所有订阅的会话
			if (topic.equals("summary")) {
				SubscriptionEndpoint.broadcast(topic, broadcastMessage);
			}
		}
	}

	private static String createBroadcastMessage(Map<String, String> clientMessages) {
		try {
			// 将去重后的消息转换成JSONArray
			ArrayNode messagesArray = objectMapper.createArrayNode();
			//按key的顺序去添加
			clientMessages.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> {
				try {
					messagesArray.add(objectMapper.readTree(entry.getValue()));
				} catch (IOException e) {
					log.error("[ws][publish]Failed to parse message: {}", e.getMessage());
				}
			});
			//转成不带换行符的json字符串
			return objectMapper.writeValueAsString(messagesArray);
		} catch (Exception e) {
			log.error("[ws][publish]Failed to create broadcast message: {}", e.getMessage());
			return "{\"error\":\"Failed to create broadcast message\"}";
		}
	}



	/**
	 * 移除所有topic中指定执行端相关消息
	 *
	 * @param clientId
	 */
	public static void removeClientMsgFromAllTopic(String clientId) {
		if (StringUtils.hasText(clientId)) {
			List<Map<String, String>> listMap = topicClientMessages.values().stream().toList();
			for (Map<String, String> map : listMap) {
				if (map.containsKey(clientId)) {
					map.remove(clientId);
				}
			}
		}
	}
}
